/*
 *  ifxconv.cpp - conversions utilities implementation
 *
 *    Copyright (c) 2001-2006, Luxena Company. All rights reserved.
 *
 *  Purpose:
 *
 */

#include <memory.h>
#include <math.h>
#include <stdlib.h>
#include <sqlhdr.h>

#include "ifxconv.h"


/////////////////////////////////////////////////////////////////////////////
// Convert logical data and sub type to informix data type

void getInformix (UINT16 uLogType, UINT16 uSubType, int2 *psqltype)
{
	//
	// values given for subtype 0
	//
	const int logical_to_informix[] =
	{
		-1,            // fldUNKNOWN
		SQLCHAR,       // fldZSTRING
		SQLDATE,       // fldDATE
		-1,            // fldBLOB
		SQLBOOL,       // fldBOOL
		SQLSMINT,      // fldINT16
		SQLINT,        // fldINT32
		SQLFLOAT,      // fldFLOAT
		SQLDECIMAL,    // fldBCD
		-1,            // fldBYTES
		SQLDTIME,      // fldTIME
		SQLDTIME,      // fldTIMESTAMP
		SQLSMINT,      // fldUINT16
		SQLINT,        // fldUINT32
		SQLFLOAT,      // fldFLOATIEEE
		-1,            // fldVARBYTES
		-1,            // fldLOCKINFO
		-1,            // fldCURSOR
		SQLINT8,       // fldINT64
		-1,            // fldUINT64
		-1,            // fldADT
		-1,            // fldARRAY
		-1,            // fldREF
		-1,            // fldTABLE
		SQLDTIME,      // fldDATETIME
		SQLDECIMAL,    // fldFMTBCD
	};

	if (uLogType >= sizeof(logical_to_informix))
	{
		*psqltype = -1;
		return;
	}

	*psqltype = logical_to_informix[uLogType];

	//
	// adjust parameter type according to logical subtype
	//
	switch (uLogType)
	{
	case fldZSTRING:
		switch (uSubType)
		{
		case fldstFIXED: *psqltype = SQLCHAR; break;
		case fldstUNICODE:
		case fldstPASSWORD: *psqltype = -1; break;
		}; break;
	case fldBLOB:
		switch (uSubType)
		{
		case fldstMEMO: *psqltype = SQLTEXT; break;
		case fldstBINARY: *psqltype = SQLBYTES; break;
		}; break;
	case fldFLOAT:
		if (uSubType == fldstMONEY)
		{
			*psqltype = SQLMONEY;
		}; break;
	case fldINT32:
		if (uSubType == fldstAUTOINC)
		{
			*psqltype = SQLSERIAL;
		}; break;
	case fldINT64:
		if (uSubType == fldstAUTOINC)
		{
			*psqltype = SQLSERIAL8;
		}; break;
	}
}


/////////////////////////////////////////////////////////////////////////////
// Convert informix data type to logical data and sub type

void getLogical (int2 sqltype, pUINT16 puLogType, pUINT16 puSubType)
{
	//
	// values given for subtype 0
	//
	const int informix_to_logical[] =
	{
		fldZSTRING,    // SQLCHAR
		fldINT16,      // SQLSMINT
		fldINT32,      // SQLINT
		fldFLOAT,      // SQLFLOAT
		fldFLOAT,      // SQLSMFLOAT
		fldBCD,        // SQLDECIMAL
		fldINT32,      // SQLSERIAL
		fldDATE,       // SQLDATE
		fldFLOAT,      // SQLMONEY
		fldUNKNOWN,    // SQLNULL
		fldDATETIME,   // SQLDTIME
		fldBLOB,       // SQLBYTES
		fldBLOB,       // SQLTEXT
		fldZSTRING,    // SQLVCHAR
		fldZSTRING,    // SQLINTERVAL
		fldZSTRING,    // SQLNCHAR
		fldZSTRING,    // SQLNVCHAR
		fldINT64,      // SQLINT8
		fldINT64,      // SQLSERIAL8
		fldUNKNOWN,    // SQLSET
		fldUNKNOWN,    // SQLMULTISET
		fldUNKNOWN,    // SQLLIST
		fldUNKNOWN,    // SQLROW
		fldUNKNOWN,    // SQLCOLLECTION
		fldUNKNOWN,    // SQLROWREF
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // reserved
		fldUNKNOWN,    // SQLUDTVAR
		fldUNKNOWN,    // SQLUDTFIXED
		fldUNKNOWN,    // SQLREFSER8
		fldUNKNOWN,    // SQLLVARCHAR
		fldUNKNOWN,    // SQLSENDRECV
		fldBOOL,       // SQLBOOL
		fldUNKNOWN,    // SQLIMPEXP
		fldUNKNOWN,    // SQLIMPEXPBIN
		fldUNKNOWN,    // SQLUDRDEFAULT
	};

	*puSubType = 0;

	if (static_cast<unsigned short>(sqltype) >= sizeof(informix_to_logical))
	{
		*puLogType = fldUNKNOWN;
		return;
	}

	*puLogType = informix_to_logical[sqltype];

	//
	// adjust subtype
	//
	switch (*puLogType)
	{
	case fldZSTRING:
		switch (sqltype)
		{
		case SQLINTERVAL:
		case SQLCHAR:
		case SQLNCHAR: *puSubType = fldstFIXED; break;
		case SQLVCHAR:
		case SQLNVCHAR: *puSubType = 0; break;
		}; break;
	case fldBLOB:
		switch (sqltype)
		{
		case SQLBYTES: *puSubType = fldstBINARY; break;
		case SQLTEXT: *puSubType = fldstMEMO; break;
		}; break;
	case fldFLOAT:
		if (sqltype == SQLMONEY)
		{
			*puSubType = fldstMONEY;
		}; break;
	case fldINT32:
		if (sqltype == SQLSERIAL)
		{
			*puSubType = fldstAUTOINC;
		}; break;
	case fldINT64:
		if (sqltype == SQLSERIAL8)
		{
			*puSubType = fldstAUTOINC;
		}; break;
	}
}


/////////////////////////////////////////////////////////////////////////////
// BCD convertion routines

void bcd_to_decimal(FMTBcd *pbcd, decimal *pdec)
{
	int iPrecision;

	//
	// if precision is 0 then value is 0
	//
	if ((iPrecision = pbcd->iPrecision) == 0)
	{
		pdec->dec_ndgts = 0;
		return;
	}

	//
	// analyze is bcd precision odd number
	//
	bool bPrecisionIsOdd = pbcd->iPrecision & 0x01;

	int iSignSpecialPlaces;

	//
	// set decimal sign
	//
	if ((iSignSpecialPlaces = pbcd->iSignSpecialPlaces) & 0x40)
		pdec->dec_pos = -1;
	else
		pdec->dec_pos = (iSignSpecialPlaces ^ 0x80) >> 7;

	//
	// find first non zero byte
	//
	int iNonZero = 0;
	pBYTE pNibble = pbcd->iFraction;
	for (; *pNibble == 0; pNibble++, iNonZero++);

	//
	// evaluate 10 based exponent of decimal
	//
	int dec_exp = iPrecision - (iSignSpecialPlaces &= 0x3F);

	//
	// convert bcd nibbles into decimal digits, count number of significant
	// digits and evaluate 100 based decimal exponent
	//
	int dec_ndgts;
	char *pDigit = pdec->dec_dgts;
	if (dec_exp & 0x01)
	{
		//
		// in this case it is needed to shift digits for one position to right
		//

		if (*pNibble & 0xF0)
		{
			*pDigit++ = *pNibble >> 4;
			dec_exp++;
			dec_ndgts = 1;
		}
		else
			dec_ndgts = 0;

		--iPrecision >>= 1;

		for (int i = iNonZero; i < iPrecision; i++)
		{
			BYTE nibble = *pNibble++; 
			*pDigit++ = (nibble & 0x0F)*10 + (*pNibble >> 4);
			dec_ndgts++;
		}

		if (!bPrecisionIsOdd)
		{
			*pDigit = (*pNibble & 0x0F)*10;
			dec_ndgts++;
		}
	}
	else
	{
		iPrecision >>= 1;

		dec_ndgts = 0;
		for (int i = iNonZero; i < iPrecision; i++)
		{
			*pDigit++ = (*pNibble >> 4)*10 + (*pNibble & 0x0F);
			pNibble++;
			dec_ndgts++;
		}

		if (bPrecisionIsOdd)
		{
			*pDigit = (*pNibble >> 4)*10;
			dec_ndgts++;
		}
	}

	//
	// set decimal exponent
	//
	pdec->dec_exp = (dec_exp >> 1) - iNonZero;

	//
	// set decimal significant digits number
	//
	pdec->dec_ndgts = dec_ndgts;
}

void decimal_to_bcd(decimal *pdec, FMTBcd *pbcd, INT16 iColPrec, INT16 iColScale)
{
	//
	// clear destinition value
	//
	memset(pbcd, 0, sizeof(*pbcd));

	int dec_ndgts, dec_exp;

	//
	// if no signicant digits nothing to convert
	//
	if ((dec_ndgts = pdec->dec_ndgts) == 0)
		return;

	BYTE iPrecision, iFraction;
	BYTE *pNibble = pbcd->iFraction;

	//
	// evaluate bcd precision and scale of decimal
	//
	if ((dec_exp = pdec->dec_exp) < 0)
	{
		int cLeadZeros = -dec_exp;
		iPrecision = static_cast<BYTE>(2*(dec_ndgts + cLeadZeros));
		iFraction = iPrecision;
		pNibble = &pNibble[cLeadZeros];
	}
	else
		if (dec_exp < dec_ndgts)
		{
			iPrecision = static_cast<BYTE>(2*dec_ndgts);
			iFraction = static_cast<BYTE>(2*(dec_ndgts - dec_exp));
		}
		else
		{
			iPrecision = static_cast<BYTE>(2*dec_exp);
			iFraction = 0;
		}

	//
	// convert decimal digits into bcd nibbles
	//
	char *pDigit = pdec->dec_dgts;
	char chDigit = *pDigit;
	char chDigitHigh = chDigit / 10, chDigitLow;
	if ((chDigitHigh == 0) && (iColPrec - iColScale & 1) && (iFraction < iPrecision))
	{
		for (int i = 1; i < pdec->dec_ndgts; i++, pNibble++)
		{
			*pNibble = (chDigit % 10) << 4;
			*pNibble |= (chDigit = *++pDigit) / 10;
		}
		*pNibble = chDigitLow = (chDigit % 10) << 4;
		iPrecision--;
	}
	else
	{
		chDigitLow = chDigit % 10;
		*pNibble++ = (chDigitHigh << 4) | chDigitLow;
		for (int i = 1; i < pdec->dec_ndgts; i++, pNibble++)
		{
			chDigit = *++pDigit;
			chDigitLow = chDigit % 10;
			*pNibble = ((chDigit / 10) << 4) | chDigitLow;
		}
	}

	if (chDigitLow == 0 && iFraction > 0)
	{
		iPrecision--;
		iFraction--;
	}

	//
	// set precision, sign bit, null bit and fraction count
	//
	pbcd->iPrecision = iPrecision;
	pbcd->iSignSpecialPlaces = 
		(pdec->dec_pos == 0 ? 0x80 : 0x00) |
		(pdec->dec_pos == -1 ? 0x40 : 0x00) |
		(iFraction);
}


/////////////////////////////////////////////////////////////////////////////
// TimeStamp conversion routines

void ctimestamp_to_dtime (pCTIMESTAMP ptstmp, dtime *pdtime)
{
	//
	// set datetime qualifier
	//
	pdtime->dt_qual = TU_CURRQUAL;

	dec_t *pdec = &pdtime->dt_dec;
	pdec->dec_exp = 7;
	pdec->dec_pos = 1;
	pdec->dec_ndgts = 9;
	char *pdgt = pdec->dec_dgts;

	//
	// set date
	//
	int year = ptstmp->year;
	*pdgt++ = year / 100;
	*pdgt++ = year % 100;
	*pdgt++ = static_cast<char>(ptstmp->month);
	*pdgt++ = static_cast<char>(ptstmp->day);

	//
	// set time
	//
	*pdgt++ = static_cast<char>(ptstmp->hour);
	*pdgt++ = static_cast<char>(ptstmp->minute);
	*pdgt++ = static_cast<char>(ptstmp->second);

	//
	// set fraction
	//
	int fraction = ptstmp->fraction;
	*pdgt++ = fraction / 10;
	*pdgt = (fraction % 10)*10;
}

void timestamp_to_dtime (pDFLOAT pstmp, dtime *pdtime)
{
	long date = static_cast<long>(*pstmp / DayMsecs) - DateDelta;
	long time = static_cast<long>(fmod(*pstmp, DayMsecs));

	CTIMESTAMP tstmp;

	//
	// get year, month, day
	//
	rjulmdy(date, reinterpret_cast<short *>(&tstmp.month));
	tstmp.year = tstmp.hour;

	//
	// get hour, minute, second and fraction
	//
	int i;
	tstmp.fraction = time % 1000;
	tstmp.second = (i = time / 1000) % 60;
	tstmp.minute = (i /= 60) % 60;
	tstmp.hour = i / 60;

	ctimestamp_to_dtime(&tstmp, pdtime);
}

void time_to_dtime (INT32 time, dtime *pdtime)
{
	//
	// set datetime qualifier
	//
	pdtime->dt_qual = TU_ENCODE(11, TU_HOUR, TU_F3);

	//
	// set decimal part
	//
	dec_t *pdec = &pdtime->dt_dec;
	pdec->dec_exp = 7;
	pdec->dec_pos = 1;
	pdec->dec_ndgts = 5;
	char *pdgt = &pdec->dec_dgts[4];
	int i = time % 1000;
	*pdgt-- = (i % 10)*10;
	*pdgt-- = i / 10;
	*pdgt-- = static_cast<char>((i = time / 1000) % 60);
	*pdgt-- = static_cast<char>((i /= 60) % 60);
	*pdgt = static_cast<char>(i / 60);
}

void dtime_to_ctimestamp (dtime *pdtime, pCTIMESTAMP ptstmp)
{
	const CTIMESTAMP tsNullDate = { 1899, 12, 30, 0, 0, 0, 0 };
	memcpy(ptstmp, &tsNullDate, sizeof(CTIMESTAMP));

	decimal *pdec = &pdtime->dt_dec;
	int dec_ndgts;

	//
	// if no signicant digits nothing to convert
	//
	if ((dec_ndgts = pdec->dec_ndgts) == 0) return;

	int dgt_pos = 0;
	char *pdgt = pdec->dec_dgts;

	int tu_pos = TU_FRAC - (pdec->dec_exp << 1);
	if (tu_pos <= 0) (tu_pos >>= 1)++;
	INT16 *pfld = reinterpret_cast<INT16 *>(ptstmp);

//
// check for exceeding significant digits
//
#define checkDigit if (++dgt_pos >= dec_ndgts) return;

	//
	// set year
	//
	if (tu_pos < TU_MONTH)
	{
		if (tu_pos == TU_YEAR)
		{
			*pfld = *pdgt++*100;
			checkDigit;
			tu_pos++;
			*pfld++ += *pdgt++;
		}
		else
			*pfld++ = *pdgt++;
		checkDigit;
		tu_pos++;
	}
	else
		pfld = reinterpret_cast<INT16 *>(&reinterpret_cast<char *>(pfld)[tu_pos]);

	//
	// set month, day, hour, minute and second
	//
	while (tu_pos < TU_FRAC)
	{
		*pfld++ = *pdgt++;
		checkDigit;
		tu_pos += 2;
	}

	//
	// set fraction
	//
	*(UINT32 *)pfld = *pdgt++ * 10;
	checkDigit;
	*(UINT32 *)pfld += *pdgt / 10;
}
